package com.zmn.mcc.permit.client.aop;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.zmn.base.common.data.common.constant.CompanyConst;
import com.zmn.base.common.data.common.dio.company.CompanyDIO;
import com.zmn.base.common.data.dubbo.interfaces.company.CompanyListRemoteService;
import com.zmn.common.constant.GlobalConsts;
import com.zmn.common.dto2.Option;
import com.zmn.common.dto2.OptionData;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.mcc.common.dto.permit.DpermitLitDO;
import com.zmn.mcc.common.dto.permit.SpermitDTO;
import com.zmn.mcc.common.dto.staff.LoginStaffDO;
import com.zmn.mcc.common.enums.DpermitEnum;
import com.zmn.mcc.dubbo.interfaces.staff.StaffPermitRemoteService;
import com.zmn.mcc.permit.client.annotation.FilterPermitData;
import com.zmn.mcc.permit.client.annotation.PermitDataType;
import com.zmn.mcc.permit.client.consts.PermitAspectTypeConsts;
import com.zmn.mcc.permit.client.model.InterceptDecisionBO;
import com.zmn.mcc.permit.client.policy.interfaces.NoPermissionPolicy;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 权限处理
 *
 * @author LuJia
 * @version v1.0
 * @since 2020/06/17 20:36
 **/
@Slf4j
public abstract class BasePermitProcess {

    public static final String PROFILES_ACTIVE_PROD = "prod";

    StaffPermitRemoteService staffPermitRemoteService;

    CompanyListRemoteService companyListRemoteService;

    @Value("${spring.profiles.active}")
    private String profile;

    /**
     * 最大权限数据 (sql in 最大数量)
     */
    public static final int MAX_PERMIT_COUNT = 500;

    /**
     * 数据权限内存缓存(半小时)
     */
    Cache<String, SpermitDTO> permitCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build();

    /**
     * 设定切入点
     */
    @SuppressWarnings("unused")
    protected abstract void pointCutMethod();

    /**
     * 取得登录用户
     *
     * @return 登录用户对象
     */
    protected abstract LoginStaffDO getLoginStaffDO();

    /**
     * 获取切面层类型
     * @return
     */
    protected abstract Integer getAspectType();

    /**
     * 切面
     *
     * @param point 连接点
     */
    @SneakyThrows
    @Around("pointCutMethod() && @annotation(filterPermitData)")
    public Object pointCut(ProceedingJoinPoint point, FilterPermitData filterPermitData) {
        // 1.获取参数
        Object[] args = point.getArgs();

        // 1.1获取参数为空抛出不合法的参数异常（加了过滤数据权限注解但该方法没有入参）
        if (Objects.isNull(args) || args.length == GlobalConsts.NONE) {
            throw new IllegalArgumentException(String.format("permit method must have param! target:%s", point.getTarget().toString()));
        }

        // 2.获取无权限策略
        Class<? extends NoPermissionPolicy> unPermissionPolicyClass = filterPermitData.value();
        // 2.1 发现无权限策略实现类被设置为接口,抛出不合法的参数异常
        if (unPermissionPolicyClass.isInterface()) {
            throw new IllegalArgumentException("UnPermissionPolicy class cannot be an interface");
        }

        // 3.取得登录用户
        LoginStaffDO loginStaff = getLoginStaffDO();

        // 4.处理数据权限并返回拦截决策对象
        InterceptDecisionBO processResultBO = processingDataPermissions(loginStaff, args, filterPermitData);

        // 5.根据拦截决策对象判断是否拦截方法,如拦截,返回拦截决策对象中携带的自定义结果
        Boolean needIntercept = processResultBO.getNeedIntercept();
        if (needIntercept) {
            return processResultBO.getReturnObject();
        }
        return point.proceed(args);
    }

    /**
     * 处理数据权限
     *
     * @param loginStaff              登录用户
     * @param targetParams            目标方法上所有的入参
     * @param unPermissionPolicyClass 无权限策略实现类
     * @return 拦截决策 对象
     */
    @SneakyThrows
    private InterceptDecisionBO processingDataPermissions(LoginStaffDO loginStaff, Object[] targetParams, FilterPermitData filterPermitData) {
        for (Object param : targetParams) {
            // 处理单个入参
            InterceptDecisionBO processResultBO = this.processSingleParameter(loginStaff, param, filterPermitData, targetParams);
            // 若此入参返回的拦截决策对象是需要拦截，则不继续处理其余入参,直接返回拦截决策对象
            if (processResultBO.getNeedIntercept()) {
                return processResultBO;
            }
        }
        // 默认不拦截
        return InterceptDecisionBO.doNotIntercept();
    }

    /**
     * 处理单个入参
     *
     * @param loginStaff              登录用户
     * @param param                     目标方法上要处理的入参对象
     * @param unPermissionPolicyClass 无权限策略实现类
     * @param targetAllParams          目标方法上所有的入参对象
     * @return 拦截决策 对象
     */
    private InterceptDecisionBO processSingleParameter(LoginStaffDO loginStaff, Object param, FilterPermitData filterPermitData, Object[] targetAllParams) throws IllegalAccessException, InstantiationException {

        Class<? extends NoPermissionPolicy> unPermissionPolicyClass = filterPermitData.value();
        
        Class<?> paramClass = param.getClass();

        // 切面类型
        boolean isDubboPermit = getAspectType() != null && Objects.equals(getAspectType(), PermitAspectTypeConsts.DUBBO);
        String limitPermitFieldName = filterPermitData.limitPermitFieldName();
        Boolean dubboLimitPermit = null;

        // k 权限大类
        Map<Integer, List<FieldPermitObj>> map = null;

        for (Field declaredField : paramClass.getDeclaredFields()) {

            // dubbo 层数据权限校验
            // 存在 用户信息 此字段值为 true : 需要过滤 <br/>
            // 不在 用户信息 or 无此字段 or 此字段值为 null : 无需过滤
            if (isDubboPermit && Objects.equals(declaredField.getName(), limitPermitFieldName)) {
                // 设置字段可访问
                ReflectionUtils.makeAccessible(declaredField);
                // 获取属性处理前已有值
                dubboLimitPermit = (Boolean) ReflectionUtils.getField(declaredField, param);
            }

            // 判断属性上是否有权限注解,无则跳过处理此属性
            boolean haveAnnotation = declaredField.isAnnotationPresent(PermitDataType.class);
            if (!haveAnnotation) {
                continue;
            }

            // 判断类型是否List<Integer> 非此类型抛出异常
            if (!isListIntegerType(declaredField)) {
                throw new UnsupportedOperationException(String.format("permit field type must be List<Integer>! field:%s", declaredField.getName()));
            }

            if (Objects.isNull(map)) {
                map = Maps.newConcurrentMap();
            }

            PermitDataType permitDataType = declaredField.getAnnotation(PermitDataType.class);
            DpermitEnum dpermitEnum = permitDataType.value();

            // 组织架构是否需要转换子公司
            boolean convSubCompany = Objects.equals(dpermitEnum, DpermitEnum.ORGANIZE_SUB) ? permitDataType.convSubCompany() : false;

            Integer oneType = dpermitEnum.getType();
            Integer twoType = dpermitEnum.getValue();
            Integer level = dpermitEnum.getLevel();

            List<FieldPermitObj> list = map.get(oneType);

            // 一级数据权限
            if (Objects.isNull(list)) {
                list = Lists.newArrayList();
                map.put(oneType, list);
            }

            // DpermitEnum value 定义重复
            boolean hasTwoType = !list.isEmpty() && list.stream().anyMatch(e -> Objects.equals(twoType, e.type));
            if (hasTwoType) {
                throw new RuntimeException("PermitDataType 值定义重复 DpermitEnum：" + dpermitEnum);
            }

            list.add(new FieldPermitObj(twoType, level, convSubCompany, param, declaredField));
        }

        // dubbo 层 不在用户信息 or 无此字段 or 此字段值为 null : 无需过滤
        if (isDubboPermit && (loginStaff == null || dubboLimitPermit == null || !dubboLimitPermit)) {
            return InterceptDecisionBO.doNotIntercept();
        }

        // 为空抛异常
        if (map == null || map.isEmpty()) {
            return InterceptDecisionBO.doNotIntercept();
        }

        log.debug("loginStaff staffId:{}, map:{}", loginStaff.getStaffId(), JSON.toJSONString(map));

        flag : for (Map.Entry<Integer, List<FieldPermitObj>> entry : map.entrySet()) {

            Integer k = entry.getKey();
            List<FieldPermitObj> v = entry.getValue();
//
//            // 校验组织架构权限下 服务公司必须和其他维度一起查询
//            if (Objects.equals(k, DpermitConsts.TYPE_ORGANIZE)) {
//                v.stream().filter(e -> Objects.equals(e.level, DpermitEnum.ORGANIZE_SP.getLevel()))
//            }

            int size = v.size();
            if (size == 1) {

                FieldPermitObj fieldPermitObj = v.get(0);

                SpermitDTO<Integer> spermitDTO = listDpermitByLoginStaffCache(loginStaff, fieldPermitObj.type, Boolean.FALSE);

                log.debug("loginStaff staffId:{}, k:{}, type:{} spermitDTO:{}", loginStaff.getStaffId(), k, fieldPermitObj.type, JSON.toJSONString(spermitDTO));

                // 有权限注解则处理此属性,并获取拦截决策对象
                InterceptDecisionBO processResultBO = processSingleField(spermitDTO, fieldPermitObj, targetAllParams, unPermissionPolicyClass);

                // 处理某个属性返回的拦截决策对象是需要拦截，则不继续处理其余属性,直接返回拦截决策对象
                if (processResultBO.getNeedIntercept()) {
                    return processResultBO;
                }

                continue;
            }

            SpermitDTO<DpermitLitDO> spermitDO = listDpermitByLoginStaffCache(loginStaff, k, false);

            log.debug("loginStaff staffId:{}, k:{}, spermitDO:{}", loginStaff.getStaffId(), k, JSON.toJSONString(spermitDO));

            // 接口响应出错 或者 无权限则直接返回
            if (!spermitDO.isSuccess() || Objects.equals(spermitDO.getRange(), SpermitDTO.RANGE_EMPTY)) {
                return processNoPermit(v.get(0).param, v.get(0).field, unPermissionPolicyClass, targetAllParams);
            }

            // 正常权限 但是权限数据集合为空 视为 无权限则直接返回
            if (Objects.equals(spermitDO.getRange(), SpermitDTO.RANGE_NORMAL) && CollectionUtils.isEmpty(spermitDO.getItems())) {
                return processNoPermit(v.get(0).param, v.get(0).field, unPermissionPolicyClass, targetAllParams);
            }

            // 单个大类的权限 为全部 则跳过
            if (Objects.equals(spermitDO.getRange(), SpermitDTO.RANGE_ALL)) {
                continue;
            }

            v.sort(Comparator.comparingInt(o -> o.level));

            // k: level v: permitData
            Map<Integer, SpermitDTO<Integer>> dpermitMap = getDpermitMapByType(loginStaff, k);

            for (FieldPermitObj fieldPermitObj : v) {


                SpermitDTO<Integer> spermitDTO = dpermitMap.get(fieldPermitObj.level);

                // 上级 或 同级别如果是选全部 则下级只处理一遍
                /*
                eg.
                组织架构选择的是啄木鸟平台
                应用的是子公司 和 服务公司
                那查询条件只需加子公司
                */
                boolean aContinue = isContinue(spermitDO, dpermitMap, fieldPermitObj);
                if (aContinue) {
                    boolean needProcessNoPermit = processNormalPermit(fieldPermitObj.param, fieldPermitObj.field, fieldPermitObj.convSubCompany, spermitDTO.getItems());
                    if (!needProcessNoPermit) {
                        continue flag;
                    }
                    return processNoPermit(fieldPermitObj.param, fieldPermitObj.field, unPermissionPolicyClass, targetAllParams);
                } else {
                    InterceptDecisionBO interceptDecisionBO = processSingleField(spermitDTO, fieldPermitObj, targetAllParams, unPermissionPolicyClass);
                    if (interceptDecisionBO.getNeedIntercept()) {
                        return interceptDecisionBO;
                    }
                }
            }
        }

        return InterceptDecisionBO.doNotIntercept();
    }

    /**
     * 获取用户 某个大类下的 数据权限集合
     * @param loginStaff 登录用户DO
     * @param type 大类型 如 组织架构 或区域
     * @return
     */
    private Map<Integer, SpermitDTO<Integer>> getDpermitMapByType(LoginStaffDO loginStaff, Integer type) {

        List<DpermitEnum> dpermitEnums = DpermitEnum.listByType(type);

        Map<Integer, SpermitDTO<Integer>> dpermitMap = Maps.newHashMapWithExpectedSize(4);

        for (DpermitEnum dpermitEnum : dpermitEnums) {
            if (dpermitEnum.getLevel() == 0) {
                continue;
            }

            SpermitDTO<Integer> spermitDTO = listDpermitByLoginStaffCache(loginStaff, dpermitEnum.getValue(), Boolean.FALSE);
            dpermitMap.put(dpermitEnum.getLevel(), spermitDTO);
        }

        return dpermitMap;
    }

    /**
     * 是否需要跳过
     *
     * @param typePermitDO
     * @param dpermitMap
     * @param fieldPermitObj
     * @return
     */
    private boolean isContinue(SpermitDTO<DpermitLitDO> typePermitDO, Map<Integer, SpermitDTO<Integer>> dpermitMap, FieldPermitObj fieldPermitObj) {

        // 往上级查询 如果有上级选的是全部 则只给本地赋值
        for (int i = fieldPermitObj.level; i > 0; i--) {
            int finalLevel = i;
            List<DpermitLitDO> collect = typePermitDO.getItems().stream().filter(e -> e.getLevel() == finalLevel).collect(Collectors.toList());
            if (collect.size() == 0) {
                continue;
            }

            SpermitDTO<Integer> spermitDTO = dpermitMap.get(finalLevel);
            int size = spermitDTO.getItems().size();

            boolean allMatch1 = collect.stream().allMatch(e -> e.getSubAll() == GlobalConsts.YES);
            if (allMatch1 && collect.size() == size) {
                return true;
            }
        }

        // 往下级查 如果下级不存在 则只给本级赋值
        Stream<DpermitLitDO> dpermitLitDOStream = typePermitDO.getItems().stream().filter(e -> e.getLevel() > fieldPermitObj.level);
        if (dpermitLitDOStream.count() == 0) {
            return true;
        }

        return false;
    }

    /**
     * 优先从缓存读取数据权限
     * @param loginStaff
     * @param dataType
     * @param clear
     * @return
     */
    private SpermitDTO listDpermitByLoginStaffCache(LoginStaffDO loginStaff, Integer dataType, Boolean clear) {
        // 非生产环境不使用缓存
//        if (!Objects.equals(profile, PROFILES_ACTIVE_PROD)) {
        if (true) {
            return staffPermitRemoteService.listDpermitByLoginStaff(loginStaff, dataType, clear);
        }
        String key = String.format("%s-%s-%s", loginStaff.getStaffId(), dataType, clear);
        SpermitDTO spermitDTO = permitCache.getIfPresent(key);
        if (Objects.nonNull(spermitDTO)) {
            return spermitDTO;
        }

        spermitDTO = staffPermitRemoteService.listDpermitByLoginStaff(loginStaff, dataType, clear);
        permitCache.put(key, spermitDTO);
        return spermitDTO;
    }

    /**
     * 处理有权限类型注解的属性
     * @param spermitDTO                数据权限对象
     * @param fieldPermitObj            数据权限字段对象
     * @param targetAllParams           方法入参集合
     * @param unPermissionPolicyClass   无权限策略实现类
     * @return 拦截决策 对象
     */
    @SneakyThrows
    private InterceptDecisionBO processSingleField(SpermitDTO<Integer> spermitDTO, FieldPermitObj fieldPermitObj, Object[] targetAllParams, Class<? extends NoPermissionPolicy> unPermissionPolicyClass) {

        int range = spermitDTO.getRange();

        // 全部权限 不处理目标属性 不拦截方法执行
        if (Objects.equals(SpermitDTO.RANGE_ALL, range)) {
            return InterceptDecisionBO.doNotIntercept();
        }
        // 数据权限列表
        List<Integer> permitList = Optional.ofNullable(spermitDTO.getItems()).orElse(Collections.emptyList());

        // 数据权限为正常权限且数据权限列表不为空, 执行普通数据权限逻辑
        if (Objects.equals(SpermitDTO.RANGE_NORMAL, range) && !permitList.isEmpty()) {

            // 数量超限截取
            int size = permitList.size();
            if (size > MAX_PERMIT_COUNT) {
                permitList = permitList.subList(0, MAX_PERMIT_COUNT);
                log.error("数据权限小类:{} 超过{}个 截取处理", fieldPermitObj.type, MAX_PERMIT_COUNT);
            }

            // 执行普通数据权限逻辑并获取是否需要执行无权限逻辑,如否,返回无需拦截,如是,继续按无权限处理
            boolean needProcessNoPermit = processNormalPermit(fieldPermitObj.param, fieldPermitObj.field, fieldPermitObj.convSubCompany, permitList);
            if (!needProcessNoPermit) {
                return InterceptDecisionBO.doNotIntercept();
            }
        }

        // 无权限调用无权限策略实现,由实现决定是否处理目标属性,并返回拦截决策对象（是否拦截方法和拦截后要返回的对象）
        // 数据对象列表为空、 rang = RANGE_EMPTY 均处理为无权限
        return processNoPermit(fieldPermitObj.param, fieldPermitObj.field, unPermissionPolicyClass, targetAllParams);
    }

    /**
     * 无权限处理
     * @param targetParam
     * @param targetField
     * @param unPermissionPolicyClass
     * @param targetAllField
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private InterceptDecisionBO processNoPermit(Object targetParam, Field targetField, Class<? extends NoPermissionPolicy> unPermissionPolicyClass, Object[] targetAllField) throws InstantiationException, IllegalAccessException {
        NoPermissionPolicy noPermissionPolicy = unPermissionPolicyClass.newInstance();
        return noPermissionPolicy.processNoPermission(targetParam, targetField, targetAllField);
    }

    /**
     * 范围数据权限处理
     *
     * @param param 目标入参
     * @param targetField 目标属性
     * @param convSubCompany 是否需要转换为子公司Id
     * @param permitList  数据权限列表
     * @return 是否需要调用无权限逻辑
     */
    private boolean processNormalPermit(Object param, Field targetField, boolean convSubCompany, List<Integer> permitList) {

        if (convSubCompany) {
            try {
                log.debug("#mcc#convSubCompany 入参：{}", permitList);
                CompanyDIO companyDIO = CompanyDIO.builder()
                        .orgThrIds(permitList)
                        .type(CompanyConst.COMPANY_TYPE_SUBSIDIARY)
                        .build();
                companyDIO.setPageSize(MAX_PERMIT_COUNT);
                ResponseDTO<OptionData<Integer>> listResponseDTO = companyListRemoteService.listPageOptionByCompanyDIO(companyDIO);
                log.debug("#mcc#convSubCompany 出参：{}", JSON.toJSONString(listResponseDTO));
                if (!listResponseDTO.isSuccess() || listResponseDTO.getData() == null) {
                    return false;
                }
                permitList = listResponseDTO.getData().getOptions().stream().map(Option::getValue).collect(Collectors.toList());
            } catch (Exception e) {
                log.error("#mcc#permit 转换子公司Id异常" + e.getMessage(), e);
            }
        }

        // 数据权限为正常权限且数据权限列表不为空, 执行普通数据权限逻辑
        if (permitList != null && !permitList.isEmpty()) {

            // 数量超限截取
            int size = permitList.size();
            if (size > MAX_PERMIT_COUNT) {
                permitList = permitList.subList(0, MAX_PERMIT_COUNT);
                log.error("#data#permit#processNormalPermit# 数据权限小类: 超过{}个 截取处理",  MAX_PERMIT_COUNT);
            }

        }

        // 设置字段可访问
        ReflectionUtils.makeAccessible(targetField);
        // 获取属性处理前已有值
        List<Integer> fieldOriginalList = (List<Integer>) ReflectionUtils.getField(targetField, param);
        // 如果已有值为null或empty，直接设置属性值为数据权限列表
        if (CollectionUtils.isEmpty(fieldOriginalList)) {
            ReflectionUtils.setField(targetField, param, permitList);
            return Boolean.FALSE;
        }
        // 已有值有数据，与数据权限列表取交集;
        Sets.SetView<Integer> intersection = Sets.intersection(Sets.newHashSet(fieldOriginalList), Sets.newHashSet(permitList));
        // 交集为空集,按无数据权限处理
        if (intersection.isEmpty()) {
            return Boolean.TRUE;
        }
        // 交集非空,设置属性为交集,返回无需拦截
        ReflectionUtils.setField(targetField, param, Lists.newArrayList(intersection));
        return Boolean.FALSE;
    }


    /**
     * 校验属性 是否是 List<Integer> 类型
     *
     * @param targetField 要校验的属性
     * @return 校验结果
     */
    private boolean isListIntegerType(Field targetField) {
        return Objects.equals(targetField.getType(), List.class)
                && Objects.equals(Integer.class, ((ParameterizedType) targetField.getGenericType()).getActualTypeArguments()[0]);
    }


    private static class FieldPermitObj{

        /**
         * 数据权限小类
         */
        Integer type;

        /**
         * 数据权限小类 等级
         */
        Integer level;

        /**
         * 转换为子公司
         */
        boolean convSubCompany;

        /**
         * 入参对象
         */
        Object param;

        /**
         * 对象字段
         */
        Field field;

        public FieldPermitObj(Integer type, Integer level, boolean convSubCompany, Object param, Field field) {
            this.type = type;
            this.level = level;
            this.convSubCompany = convSubCompany;
            this.param = param;
            this.field = field;
        }

        public FieldPermitObj() {
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("{");
            sb.append("\"type\":")
                    .append(type);
            sb.append(",\"level\":")
                    .append(level);
            sb.append(",\"convSubCompany\":")
                    .append(convSubCompany);
            sb.append(",\"param\":")
                    .append(param);
            sb.append(",\"field\":")
                    .append(field);
            sb.append('}');
            return sb.toString();
        }
    }
}
